/*
Xern Entity Relocation Network
Copyright (C) 2011 Ronie Salgado <roniesalg@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include <boost/uuid/uuid_generators.hpp>
#include "MapLoader.hpp"
#include "tinyxml.h"

namespace MapXern
{
	static boost::uuids::random_generator GenerateId;
	static boost::uuids::string_generator ReadGuid;

    static float ReadFloat(const char *attr, const TiXmlElement *node, float def=0.0f)
    {
        const char *value = node->Attribute(attr);
        if(!value)
            return def;
        return atof(value);
    }

    static int ReadInteger(const char *attr, const TiXmlElement *node, int def=0.0f)
    {
        const char *value = node->Attribute(attr);
        if(!value)
            return def;
        return atoi(value);
    }

    static Vector3 ReadVector3(const TiXmlElement *node)
    {
        Vector3 ret(ReadFloat("x", node), ReadFloat("y", node), ReadFloat("z", node));
        return ret;
    }

    static Quaternion ReadQuaternion(const TiXmlElement *node)
    {
        Quaternion ret(ReadFloat("w", node), ReadFloat("x", node), ReadFloat("y", node), ReadFloat("z", node));
        return ret;
    }

    static boost::shared_ptr<Brush> ReadMapBrush(const TiXmlElement *node, const Transform &mapTrans, float mapScale)
    {
        boost::shared_ptr<Brush> ret(new Brush());

        // Read the guid or generate one,
        const char *guid = node->Attribute("guid");
        if(guid)
        	ret->id = ReadGuid(guid);
        else
        	ret->id = GenerateId();

        // Read all of the faces.
        const TiXmlElement *faceNode = node->FirstChildElement("face");
        for(; faceNode; faceNode = faceNode->NextSiblingElement("face"))
        {
            // Create the brush face.
            BrushFace face;

            // Read the plane points.
            Vector3 p1, p2, p3;
            p1.Set(ReadFloat("ax", faceNode), ReadFloat("ay", faceNode), ReadFloat("az", faceNode));
            p2.Set(ReadFloat("bx", faceNode), ReadFloat("by", faceNode), ReadFloat("bz", faceNode));
            p3.Set(ReadFloat("cx", faceNode), ReadFloat("cy", faceNode), ReadFloat("cz", faceNode));

            face.mapOffset = Vector2(ReadFloat("xoff", faceNode), ReadFloat("yoff", faceNode));
            face.mapScale = Vector2(ReadFloat("xscale", faceNode), ReadFloat("yscale", faceNode));
            face.mapRot = ReadFloat("rot", faceNode);

            face.globalScale = mapScale;

            const char *texture = faceNode->Attribute("texture");
            if(texture)
            	face.textureName = texture;

            const char *twidth = faceNode->Attribute("twidth");
            const char *theight = faceNode->Attribute("theight");
            if(twidth && theight)
            	face.textureSize = Vector2(atof(twidth), atof(theight));

            // Create the face plane.
            Vector3 tv = mapTrans.GetVector();
            face.plane.Set(mapTrans*p1, mapTrans*p2, mapTrans*p3);
            Matrix3 tm = mapTrans.GetMatrix();

            // Store the face.
            ret->faces.push_back(face);
        }

        // Return the brush.
        return ret;
    }

    static boost::shared_ptr<Entity> ReadMapEntity(const TiXmlElement *node)
    {
        // Create the entity.
        boost::shared_ptr<Entity> ret(new Entity());

        // Read the class name.
        const char *className = node->Attribute("className");
        if(className)
            ret->className = className;

        // Read the properties.
        const TiXmlElement *propNode = node->FirstChildElement("prop");
        for(; propNode; propNode = propNode->NextSiblingElement("prop"))
        {
            const char *name = propNode->Attribute("name");
            const char *value = propNode->Attribute("value");
            if(!name || !value)
                continue;

            ret->properties.insert(std::make_pair(name, value));
        }

        // Return the entity.
        return ret;
    }

    static boost::shared_ptr<Object> ReadMapObject(const TiXmlElement *node, const Transform &mapTrans, const Transform &mapTransNoScale, float mapScale)
    {
        // Create the object.
        boost::shared_ptr<Object> ret(new Object());
    
        // Read the name(optional).
        const char *name = node->Attribute("name");
        if(name)
            ret->name = name;

        // Read the guid or generate one,
        const char *guid = node->Attribute("guid");
        if(guid)
        	ret->id = ReadGuid(guid);
        else
        	ret->id = GenerateId();

        // Read the position(optional).
        Transform &trans = ret->transform;
        const TiXmlElement *positionNode = node->FirstChildElement("position");
        if(positionNode)
            trans.SetVector(mapTrans * ReadVector3(positionNode));

        Matrix3 mat;

        // Read the rotation as pitch-yaw-roll
        const TiXmlElement *orientationNode = node->FirstChildElement("orientation");
        if(orientationNode)
        {
        	float pitch = ReadFloat("pitch", orientationNode);
        	float yaw = ReadFloat("yaw", orientationNode);
        	float roll = ReadFloat("roll", orientationNode);
        	mat = XYZRotMatrix3(pitch, yaw, roll);
        }

        // Read the rotation as a quaternion.
        orientationNode = node->FirstChildElement("qorientation");
        if(orientationNode)
        	mat = QRotMatrix3(ReadQuaternion(orientationNode));

        // Read the scale.
        const TiXmlElement *scaleNode = node->FirstChildElement("scale");
        if(scaleNode)
        	mat = mat * ScaleMatrix3(ReadVector3(scaleNode));

        // Add the map transform.
        mat = mapTransNoScale.GetMatrix() * mat * mapTransNoScale.GetMatrix().Inverse();

        // Store the matrix.
        trans.SetMatrix(mat);

        // Read the entity (optional).
        const TiXmlElement *entityNode = node->FirstChildElement("entity");
        if(entityNode)
            ret->entity = ReadMapEntity(entityNode);

        // Read the brush (optional.
        const TiXmlElement *brushNode = node->FirstChildElement("brush");
        if(brushNode)
            ret->brush = ReadMapBrush(brushNode, mapTrans, mapScale);

        // Read the children(optional).
        const TiXmlElement *childrenNode = node->FirstChildElement("children");
        if(childrenNode)
        {
            const TiXmlElement *childNode = childrenNode->FirstChildElement("object");
            for(; childNode; childNode = childNode->NextSiblingElement("object"))
            {
                ret->children.push_back(ReadMapObject(childNode, mapTrans, mapTransNoScale, mapScale));
            }
        }

        // Return the readed object.
        return ret;        
    }

    boost::shared_ptr<Object> LoadMap(const std::string &filename, const Transform &conversion, const Transform &conversionNoScale,
    		float mapScale)
    {
        // Open the file.
        TiXmlDocument doc;
        if(!doc.LoadFile(filename.c_str()))
        {
            fprintf(stderr, "Failed to load file '%s': %s\n", filename.c_str(), doc.ErrorDesc());
            return boost::shared_ptr<Object> ();
        }

        // Get the root element.
        const TiXmlElement *root = doc.RootElement();
        if(!root)
        {
            fprintf(stderr, "The map file '%s' is empty.\n", filename.c_str());
            return boost::shared_ptr<Object> ();
        }

        // Get the root object.
        const TiXmlElement *rootObject = root->FirstChildElement("object");
        if(!rootObject)
        {
            fprintf(stderr, "The map file '%s' doesn't have a root object.\n", filename.c_str());
            return boost::shared_ptr<Object> ();
        }

        // Read the root object.
        return ReadMapObject(rootObject, conversion, conversionNoScale, mapScale);
    }
}
